package com.my.study.common.utils;

import com.my.study.common.security.AbstractCustomAuthenticationToken;
import com.my.study.common.security.InnerCustomAuthenticationToken;
import com.my.study.dto.JwtUserDto;
import com.my.study.entity.MyUserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author Carlos
 * @version 1.0.0
 * @date 2022/8/3 23:50
 */
public final class JwtTokenUtils {

    /**
     * 用户ID
     */
    private static final String USER_ID = Claims.ID;

    /**
     * 用户名称
     */
    private static final String USER_NAME = Claims.SUBJECT;

    /**
     * 创建时间
     */
    private static final String CREATED = "created";
    /**
     * 权限列表
     */
    private static final String AUTHORITIES = "authorities";
    /**
     * 密钥
     */
    private static final String SECRET = "hZs_kJt_xXy_oA_330100";

    /**
     * 有效期12小时
     */
    private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000;
    
    public static final JwtUserDto DEFAULT_USER = new JwtUserDto(new MyUserEntity());

    private JwtTokenUtils() {
    }

    /**
     * 生成令牌
     *
     * @param authentication 验证信息
     * @return 令牌
     */
    public static String generateToken(Authentication authentication) {
        Map<String, Object> claims = new HashMap<>(3);
        JwtUserDto jwtUserDto = getUserFromAuthentication(authentication);

        claims.put(USER_NAME, jwtUserDto.getMyUserEntity().getUserName());
        claims.put(USER_ID, jwtUserDto.getMyUserEntity().getUserId());
        claims.put(CREATED, new Date());
        claims.put(AUTHORITIES, authentication.getAuthorities());
        return generateToken(claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private static String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }

    /**
     * 根据请求令牌获取登录认证信息
     *
     * @param request HTTP请求
     * @return Authentication
     */
    public static Authentication getAuthenticationFromToken(HttpServletRequest request) {
        Authentication authentication = null;
        // 获取请求携带的令牌
        String token = JwtTokenUtils.getToken(request);
        // 请求令牌不为空
        if (token != null) {
            if (SecurityUtils.getAuthentication() == null) {
                // 上下文中Authentication为空
                Claims claims = getClaimsFromToken(token);
                if (claims == null) {
                    return null;
                }
                String userId = claims.getId();
//                if (userId == null) {
//                    return null;
//                }
                if (isTokenExpired(token)) {
                    return null;
                }
                Object authors = claims.get(AUTHORITIES);
                List<GrantedAuthority> authorities = dealGrantedAuthority(authors);
                
                MyUserEntity user = new MyUserEntity();
                //user.setUserId(Integer.parseInt(userId));
                user.setUserName(claims.getSubject());
                JwtUserDto jwtUserDto = new JwtUserDto(user);
                jwtUserDto.setAuthorities(authorities);
                authentication = new InnerCustomAuthenticationToken(jwtUserDto);
            }
            else {
                if (validateToken(token, SecurityUtils.getUsername())) {
                    // 如果上下文中Authentication非空，且请求令牌合法，直接返回当前登录认证信息
                    authentication = SecurityUtils.getAuthentication();
                }
            }
        }
        return authentication;
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public static String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 验证令牌
     *
     * @param token
     * @param username
     * @return
     */
    public static Boolean validateToken(String token, String username) {
        String userName = getUsernameFromToken(token);
        return (userName.equals(username) && !isTokenExpired(token));
    }

    /**
     * 刷新令牌
     *
     * @param token token
     * @return 新的token
     */
    public static String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put(CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期，true - 是
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取请求token
     *
     * @param request HTTP请求
     * @return token
     */
    public static String getToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        String tokenHead = "Bearer ";
        if (token == null) {
            token = request.getHeader("token");
        } else if (token.contains(tokenHead)) {
            token = token.substring(tokenHead.length());
        }
        if ("".equals(token)) {
            token = null;
        }
        return token;
    }
    
    @SuppressWarnings("rawtypes")
    private static List<GrantedAuthority> dealGrantedAuthority(Object authors) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        if (authors instanceof List) {
            for (Object object : (List) authors) {
                Object authority = ((Map) object).get("authority");
                authorities.add(new SimpleGrantedAuthority((String) authority));
            }
        }
        return authorities;
    }
    
    public static JwtUserDto  getUserFromAuthentication(Authentication authentication) {
        JwtUserDto jwtUserDto = DEFAULT_USER;
        if (authentication instanceof AbstractCustomAuthenticationToken) {
            jwtUserDto = ((AbstractCustomAuthenticationToken) authentication).getJwtUserDto();
        } else if (authentication instanceof RememberMeAuthenticationToken) {
            jwtUserDto = ((JwtUserDto) authentication.getPrincipal());
        } else if (authentication instanceof OAuth2LoginAuthenticationToken) {
            jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        }
        return jwtUserDto;
    }
    
}
